using System;
using System.Reflection;
using Crainiate.Data.Persistence;
using Crainiate.Data.Persistence.Cache;
using Crainiate.Data.Persistence.Providers;

namespace Crainiate.Data.Persistence.Modelling.Entities
{
    [Serializable]
	public abstract class Relationship<ParentType, ChildType>: Entity
    where ParentType: class, IPersistable
    where ChildType: class, IPersistable 
	{
		#region Interface

		//Constructors
		public Relationship()
		{
		}

        public Relationship(Context context): base(context)
        {
        }
		
		//Properties


        //Methods
        public virtual Results<ChildType> Children(ParentType parent)
        {
            return GetChildren(parent, null);
        }

        protected virtual ICondition CreateCondition(PropertyInfo field, object value)
        {
            return new Condition(field, value);
        }

        protected virtual IColumns CreateColumns(PropertyInfo[] properties)
        {
            return new Columns(properties);
        }

		#endregion

        #region Implementation

        private Results<ChildType> GetChildren(ParentType parent, IConditions conditions)
        {
            //Get the two reflection objects
            ReflectionCacheItem cache = Component.Instance.ReflectionCache.Lookup(GetType());
            ReflectionCacheItem parentCache = Component.Instance.ReflectionCache.Lookup(typeof(ParentType));
            ReflectionCacheItem childCache = Component.Instance.ReflectionCache.Lookup(typeof(ChildType));

            //Validate the items
            if (!parentCache.HasPrimaryKey) throw new RelationshipException("Parent must have a primary key defined.");
            if (!cache.HasCompoundKeys) throw new RelationshipException("Relationship must have compound keys.");
            if (cache.CompoundKeys.Length != 2) throw new RelationshipException("Relationship must exactly two compound keys.");
            if (!childCache.HasPrimaryKey) throw new RelationshipException("Child must have a primary key defined.");

            //Create a new query to get all the relationships and children for a parent
            Type genericType = typeof(Query<>).MakeGenericType(this.GetType());
            IQuery query = (IQuery)Activator.CreateInstance(genericType);

            //Take the value of the parent and place it in the first compound key
            PropertyInfo prop = cache.CompoundKeys[0];
            object value = parentCache.PrimaryKey.GetValue(parent, null);

            ICondition condition = CreateCondition(prop, value);
            query.Where.Add(condition);

            //Add the subquery
            IQuery subQuery = new Query<ChildType>();
            subQuery.Where = conditions;
            subQuery.JoinFrom = CreateColumns(new PropertyInfo[] { cache.CompoundKeys[1] });
            subQuery.JoinTo = CreateColumns(new PropertyInfo[] { cache.PrimaryKey });

            //Execute the query
            query.Fill = false; //Dont fill main query
            query.Execute();

            return subQuery.Results as Results<ChildType>;
        }

        #endregion

    }
}
